package com.emagic.manage.modules.room.view;

import android.content.Context;
import android.graphics.ImageFormat;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;

import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.JsonHttpResponseHandler;
import com.loopj.android.http.RequestParams;

import org.apache.http.Header;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.webrtc.DataChannel;
import org.webrtc.IceCandidate;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.SessionDescription;
import org.webrtc.VideoRenderer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import fi.vtt.nubomedia.kurentoroomclientandroid.KurentoRoomAPI;
import fi.vtt.nubomedia.kurentoroomclientandroid.RoomError;
import fi.vtt.nubomedia.kurentoroomclientandroid.RoomListener;
import fi.vtt.nubomedia.kurentoroomclientandroid.RoomNotification;
import fi.vtt.nubomedia.kurentoroomclientandroid.RoomResponse;
import fi.vtt.nubomedia.utilitiesandroid.LooperExecutor;
import fi.vtt.nubomedia.webrtcpeerandroid.NBMMediaConfiguration;
import fi.vtt.nubomedia.webrtcpeerandroid.NBMPeerConnection;
import fi.vtt.nubomedia.webrtcpeerandroid.NBMWebRTCPeer;


/**
 * Created by CYJ on 2017/3/14.
 */
public class ClassRoomManager implements RoomListener, NBMWebRTCPeer.Observer {

    // definitions
    //public static
    public static int ERR_OK = 0;
    public static int ERR_INTERNAL_EXCEPTION = -1;
    public static int ERR_NOT_INITIALIZED = 1;
    public static int ERR_INVALID_STATUS = 2;
    public static int ERR_HTTP_REQUEST_FAILED = 3;
    public static int ERR_BAD_PARAMETERS = 4;

    // public variables

    // private static
    private final static String TAG = "ClassRoomManager";
    private static ClassRoomManager Instance;
    private static boolean globalInitialized = false;

    private final static Integer Lock = 1;
    private static volatile Handler ApplicationHandler = null;
    private static volatile Context ApplicationContext = null;
    private static boolean _ut = false;
    private static AsyncHttpClient client = new AsyncHttpClient();

    private static int STATUS_IDLE = 0;
    private static int STATUS_CHECKING = 1;
    private static int STATUS_GETTINGCFG = 2;
    private static int STATUS_CONNECTING = 3;
    private static int STATUS_CONNECTED = 4;
    private static int STATUS_JOINING = 5;
    private static int STATUS_ALLREADY = 6;
    private static int STATUS_DISCONNECTING = 7;

    private static int REQUEST_TYPE_JION = 1;
    private static int REQUEST_TYPE_LEAVE = 2;
    private static int REQUEST_TYPE_PUB = 3;
    private static int REQUEST_TYPE_SUB = 4;
    private static int REQUEST_TYPE_ICE = 5;
    private static int REQUEST_TYPE_UNPUB = 6;
    private static int REQUEST_TYPE_UNSUB = 7;

    private static int REQUEST_ID_UNIVERSAL = 99;

    private static String HTTP = "http";
    private static String WEBFUNC_CHECKMEETING = "/ClientAPI/checkroom";
    private static String WEBFUNC_GETCONFIG = "/ClientAPI/getconfig";
    private static String WEBFUNC_GETROOMFILE = "/ClientAPI/getroomfile";
    private static String WEBFUNC_GETGIFTINFO = "/ClientAPI/getgiftinfo";

    // private variables
    private LooperExecutor _executor;
    private KurentoRoomAPI _room = null;
    private NBMWebRTCPeer _media = null;
    private IClassRoomManagerCbk _cbk = null;

    private ClassRoomUser _myself = null;
    private String _room_uri = null;
    private String _room_id = null;
    private int _room_type = 0;//0:1v1, 1:small class
    private String _room_name = null;
    private String _user_type = null;
    private JSONObject _room_properties = null;
    private List<AllRoomUser.RoomUser> _room_users = null;
    private String _room_ip = null;

    private String _giftinfo = null;

    private boolean _hasDatachannel = false;
    private boolean _useOpenGL = true;
    private VideoRenderer.Callbacks _localView = null;
    private NBMMediaConfiguration _configuration = null;

    public HashMap<String, ClassRoomUser> get_users() {
        return _users;
    }

    private HashMap<String, ClassRoomUser> _users = new HashMap<String, ClassRoomUser>();
    private List<RoomFile> filesList = new ArrayList<>();

    private HashMap<Integer, ClassRoomManagerAsyncHandler> _callBackHandlers = new HashMap<Integer, ClassRoomManagerAsyncHandler>();
    private int _status = STATUS_IDLE;
    private int _req_seq = 100;// !!!changed in main thread!!!

    public void set_status(int _status) {
        this._status = _status;
    }

    private class Request {
        public int reqType;
        public int reqSeq;
        public String reqParam;
        public String reqOtherParam;

        public Request(int type, int seq, String param, String otherParam) {
            reqType = type;
            reqSeq = seq;
            reqParam = param;
            reqOtherParam = otherParam;
        }
    }

    private HashMap<String, Object> _renders = new HashMap<String, Object>();
    private HashMap<Integer, Request> _requests = new HashMap<Integer, Request>();

    // public functions
    public static void globalInitialize(Context applicationContext) {
        synchronized (Lock) {
            ApplicationHandler = new Handler(applicationContext.getMainLooper());
            ApplicationContext = applicationContext;
        }
    }

    public static ClassRoomManager getInstance() {
        ClassRoomManager localInstace = Instance;
        if (localInstace == null) {
            synchronized (ClassRoomManager.class) {
                localInstace = Instance;
                if (localInstace == null) {
                    Instance = localInstace = new ClassRoomManager();
                }
            }
        }
        return localInstace;
    }

    static void logd(String log) {
        if (!_ut) // running on a real device
            Log.d(TAG, log);
        else
            System.out.println(log);
    }

    static void loge(String log) {
        if (!_ut) // running on a real device
            Log.e(TAG, log);
        else
            System.out.println(log);
    }

    static void RunOnUIThread(Runnable runnable) {

        if (ApplicationHandler == null
                || Thread.currentThread() == Looper.getMainLooper().getThread()) {
            runnable.run();
        } else {
            synchronized (Lock) {
                if (ApplicationHandler != null)
                    ApplicationHandler.post(runnable);
            }
        }
    }

    void setUTAdapters(KurentoRoomAPI room, NBMWebRTCPeer peerConnection) {
        _ut = true;
        _room = room;
        _media = peerConnection;
    }

    public void setCallbBack(IClassRoomManagerCbk callBack) {
        _cbk = callBack;
    }

    //enterroom://start?host=192.168.0.182&port=8443&nickname=wangshubo&meetingname=jkkjkjjk&param=DfArEiAVkE
    public int joinRoom(final String ip, final String webip, String port,
                        String webport, String nickname, RequestParams params,
                        VideoRenderer.Callbacks renderer, boolean useOpenGL,
                        String type) {

        if ((ApplicationContext == null && !_ut) || _cbk == null)
            return ERR_NOT_INITIALIZED;

        if (_status != STATUS_IDLE)
            return ERR_INVALID_STATUS;

//        _room_uri = "wss://" + "media01.weiyicloud.com" + ":" + port + "/room";
        _room_uri = "wss://" + "media01.weiyiclass.com" + ":" + port + "/room";
        _room_id = null;
        _room_type = 0;
        _room_name = null;
        _room_properties = null;
        _room_users = new ArrayList<>();
        _user_type = type;

        _useOpenGL = useOpenGL;
        _localView = renderer;
        _configuration = null;

        _myself = new ClassRoomUser();
        _myself.peerId = "";
        _myself.nickName = nickname;
        _myself.hasAudio = true;
        _myself.hasVideo = _localView != null;
        _myself.publishState = 0;

        client = new AsyncHttpClient(true, 80, 443);

        //step1 checkMeeting
        //step2 getconfig & initmedia
        //step3 connect
        //step4 join
        if (_ut) {
            step3Connect();
            return ERR_OK;
        }

        step1CheckMeeting(webip, webport, params, new ClassRoomManagerAsyncHandler() {
            @Override
            public void onComplete(final int error) {

                Log.e(TAG, "step1CheckMeeting ret=" + error);
                if (error == 0) {
                    if (!"teacher".equals(type)) {
                        step2GetGiftInfo(webip, webport, new ClassRoomManagerAsyncHandler() {
                            @Override
                            public void onComplete(int error) {
                                if (error == 0) {
                                    if (!TextUtils.isEmpty(_giftinfo)) {
                                        _cbk.roomGiftInfo(_giftinfo);
                                    }
                                } else {
                                    _cbk.roomGiftInfo("0");
                                }

                            }
                        });
                    }
                    step2GetRoomFile(webip, webport, new ClassRoomManagerAsyncHandler() {
                        @Override
                        public void onComplete(final int error) {
                            if (error == 0) {
                                if (filesList != null && filesList.size() > 0) {
                                    _cbk.roomFileInfo(filesList);
                                }
                            }
                        }
                    });
                    step2InitMedia();
                    step2GetConfig(webip, webport, new ClassRoomManagerAsyncHandler() {
                        @Override
                        public void onComplete(final int error) {

                            Log.e(TAG, "step2GetConfig:onComplete: " + error);
                            if (error == 0) {
                                step3Connect();
                            } else {
                                _cbk.roomManagerDidFailWithError(error);
                            }
                        }
                    });
                } else {
                    _cbk.roomManagerDidFailWithError(error);
                }
            }
        });
        Log.e(TAG, "joinRoom: ");
        return ERR_OK;
    }

    public int leaveRoom() {
        if (_status == STATUS_IDLE)
            return ERR_INVALID_STATUS;

        int curStatus = _status;
        setStatus(STATUS_DISCONNECTING);
        if (curStatus < STATUS_CONNECTING) {
            client.cancelAllRequests(true);
            setStatus(STATUS_IDLE);
        } else if (curStatus == STATUS_DISCONNECTING) {
            client.cancelAllRequests(true);
            setStatus(STATUS_IDLE);
        } else {
            if (curStatus == STATUS_CONNECTING)
                _room.disconnectWebSocket();

            _room.sendLeaveRoom(REQ(REQUEST_TYPE_LEAVE));
        }

        return ERR_OK;
    }

    public int getRoomType() {
        return _room_type;
    }

    public final String getRoomName() {
        return _room_name;
    }

    public final JSONObject getRoomProperties() {
        return _room_properties;
    }

    public final List<AllRoomUser.RoomUser> getRoomUsers() {
        return _room_users;
    }

    public final ClassRoomUser getMySelf() {
        return _myself;
    }

    public int changeUserProperty(String peerId, String tellWhom, String key, Object value) {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        if (key == null || key.isEmpty() || value == null)
            return ERR_BAD_PARAMETERS;

        HashMap<String, Object> property = new HashMap<String, Object>();
        property.put(key, value);

        HashMap<String, Object> params = new HashMap<String, Object>();
        params.put("id", peerId);
        params.put("toID", tellWhom);
        params.put("properties", property);
        _room.sendExtendedRequest("setProperty", params, REQUEST_ID_UNIVERSAL);

        return ERR_OK;
    }

    public int changeUserProperty(String peerId, String tellWhom, Object property) {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        HashMap<String, Object> params = new HashMap<String, Object>();
        params.put("id", peerId);
        params.put("toID", tellWhom);
        params.put("name", "updataProperty");
        params.put("properties", property);
        _room.sendExtendedRequest("setProperty", params, REQUEST_ID_UNIVERSAL);

        return ERR_OK;
    }

    public int changeUserPublish(String peerID, int publish) {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        changeUserProperty(peerID, "__all", "publishstate", new Integer(publish));
        return ERR_OK;
    }

    public int sendMessage(String message) {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        _room.sendMessage(_room_id, _myself.peerId, message, REQUEST_ID_UNIVERSAL);
        return ERR_OK;
    }

    public int pubMsg(String msgName, String msgId, String toId, Object data) {
        Log.e(TAG, "pubMsg: _status:" + _status);
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        HashMap<String, Object> params = new HashMap<String, Object>();
        params.put("name", msgName);
        params.put("id", msgId);
        params.put("toID", toId);
        if (data == null) {
            params.put("data", "{}");
        } else {
            params.put("data", data);
        }
        _room.sendExtendedRequest("pubMsg", params, REQUEST_ID_UNIVERSAL);
        return ERR_OK;
    }

    public int pubMsg(HashMap<String, Object> params) {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        _room.sendExtendedRequest("pubMsg", params, REQUEST_ID_UNIVERSAL);
        return ERR_OK;
    }

    public int delMsg(String msgName, String msgId, String toId, Object data) {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        HashMap<String, Object> params = new HashMap<String, Object>();
        params.put("name", msgName);
        params.put("id", msgId);
        params.put("toID", toId);
        if (data == null) {
            params.put("data", "{}");
        } else {
            params.put("data", data);
        }

        _room.sendExtendedRequest("delMsg", params, REQUEST_ID_UNIVERSAL);
        return ERR_OK;
    }

    public int delMsg(HashMap<String, Object> params) {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        _room.sendExtendedRequest("delMsg", params, REQUEST_ID_UNIVERSAL);
        return ERR_OK;
    }

    public int evictUser(String peerID) {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("id", peerID);
        _room.sendExtendedRequest("evictParticipant", map, REQUEST_ID_UNIVERSAL);
        return ERR_OK;
    }

    //Media
    public int selectCameraPosition(NBMMediaConfiguration.NBMCameraPosition position) {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        _media.selectCameraPosition(position);
        return ERR_OK;
    }

    public boolean isVideoEnabled() {
        return (_status == STATUS_ALLREADY && _media.videoEnabled() && _media.videoAuthorized());
    }

    public int enableVideo(boolean enable) {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        _media.enableVideo(enable);
        return ERR_OK;
    }

    public boolean isAudioEnabled() {
        return (_status == STATUS_ALLREADY && _media.audioEnabled() && _media.audioAuthorized());
    }

    public int enableAudio(boolean enable) {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        _media.enableAudio(enable);
        return ERR_OK;
    }

    // private functions
    private int REQ(int type) {
        return REQ(type, null, null);
    }

    private int REQ(int type, String param) {
        return REQ(type, param, null);
    }

    private int REQ(int type, String param, String other) {
        int seq = 0;
        synchronized (_requests) {
            seq = _req_seq++;
            _requests.put(seq, new Request(type, seq, param, other));
        }
        return seq;
    }

    private void step1CheckMeeting(final String ip, String port, final RequestParams params,
                                   final ClassRoomManagerAsyncHandler onComplete) {
        setStatus(STATUS_CHECKING);
        String url = "https://" + ip + ":" + port + WEBFUNC_CHECKMEETING;

        String p = params.toString();
        Log.e(TAG, "step1CheckMeeting: params:" + p);
        Log.e(TAG, "step1CheckMeeting: url:" + p);


        client.post(url, params, new JsonHttpResponseHandler() {
            @Override
            public void onSuccess(int statusCode, Header[] headers, final JSONObject response) {
                Log.e(TAG, "onSuccess:response: " + response);
                RunOnUIThread(new Runnable() {
                    @Override
                    public void run() {

                        try {
                            int nRet = response.getInt("result");
                            JSONObject meeting = null;
                            if (nRet == 0) {
                                meeting = response.getJSONObject("room");
                                if (meeting.isNull("serial"))
                                    nRet = ERR_INTERNAL_EXCEPTION;
                                else {
                                    _room_properties = meeting;
//                                    try {
//                                        JSONArray array = new JSONArray(response.getString("roomuser"));
//                                        for (int i = 0; i < array.length(); i++) {
//                                            JSONObject ob = (JSONObject) array.get(i);//得到json对象
//                                            AllRoomUser.RoomUser roomUser = new AllRoomUser.RoomUser();
//                                            roomUser.setAccount(ob.getString("account"));
//                                            roomUser.setFirstname(ob.getString("firstname"));
//                                            roomUser.setReceiveid(ob.getString("receiveid"));
//                                            roomUser.setSendid(ob.getString("sendid"));
//                                            roomUser.setSerial(ob.getString("serial"));
//
//                                            _room_users.add(roomUser);
//                                        }
//                                    } catch (JSONException e) {
//                                        Log.e(TAG, "run: " + e.getMessage());
//                                    }


                                    _room_id = meeting.getString("serial");
                                    _room_name = meeting.getString("roomname");
                                    _room_type = meeting.optInt("roomtype");

                                    _myself.role = response.optInt("roomrole");
                                    String peerId = response.optString("thirdid");
                                    AllRoomUser.RoomUser roomUser = new AllRoomUser.RoomUser();
                                    roomUser.setSerial(_room_id);
                                    roomUser.setFirstname(peerId);
                                    roomUser.setAccount(peerId);
                                    roomUser.setReceiveid(peerId);
                                    roomUser.setSendid(peerId);
                                    roomUser.setFirstname(peerId);
                                    _room_users.add(roomUser);
                                    if (peerId != null && !peerId.isEmpty())
                                        _myself.peerId = peerId;
                                    else if (_myself.peerId == null || _myself.peerId.isEmpty())
                                        _myself.peerId = java.util.UUID.randomUUID().toString();
                                }
                            }

                            onComplete.onComplete(nRet);
                        } catch (JSONException e) {
                            e.printStackTrace();
                            onComplete.onComplete(ERR_INTERNAL_EXCEPTION);
                        }
                    }
                });
            }

            @Override
            public void onFailure(final int statusCode, Header[] headers, String responseString, Throwable throwable) {
                Log.e(TAG, "onFailure: " + responseString);
                RunOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        onComplete.onComplete(ERR_HTTP_REQUEST_FAILED);
                    }
                });
            }

            @Override
            public void onFailure(final int statusCode, Header[] headers, Throwable throwable, JSONObject object) {
                Log.e(TAG, "onFailure1:statusCode: " + statusCode);
                RunOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        onComplete.onComplete(ERR_HTTP_REQUEST_FAILED);
                    }
                });
            }
        });
    }

    private void step2GetGiftInfo(final String ip, final String port, final ClassRoomManagerAsyncHandler onComplete) {
        if (_room_id == null) {
            onComplete.onComplete(ERR_HTTP_REQUEST_FAILED);
            return;
        }
        String url = "https://" + ip + ":" + port + WEBFUNC_GETGIFTINFO;
        RequestParams params = new RequestParams();
        params.put("serial", _room_id);
        params.put("receiveid", _myself.peerId);
        client.post(url, params, new JsonHttpResponseHandler() {
            @Override
            public void onSuccess(int statusCode, Header[] headers, final JSONObject response) {
                RunOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        JSONArray giftinfo = null;
                        JSONObject gift = null;
                        try {
                            int nRet = response.getInt("result");
                            giftinfo = response.getJSONArray("giftinfo");
                            gift = giftinfo.getJSONObject(0);
                            _giftinfo = gift.getString("giftnumber");
                            onComplete.onComplete(nRet);
                        } catch (JSONException e) {
                            e.printStackTrace();
                            onComplete.onComplete(ERR_INTERNAL_EXCEPTION);
                        }

                    }
                });
            }

            @Override
            public void onFailure(final int statusCode, Header[] headers, String responseString, Throwable throwable) {
                RunOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        onComplete.onComplete(ERR_HTTP_REQUEST_FAILED);
                    }
                });
            }

            @Override
            public void onFailure(final int statusCode, Header[] headers, Throwable throwable, JSONObject object) {
                RunOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        onComplete.onComplete(ERR_HTTP_REQUEST_FAILED);
                    }
                });
            }
        });
    }

    private void step2GetRoomFile(final String ip, final String port, final ClassRoomManagerAsyncHandler onComplete) {
        if (_room_id == null) {
            onComplete.onComplete(ERR_HTTP_REQUEST_FAILED);
            return;
        }

        String url = "https://" + ip + ":" + port + WEBFUNC_GETROOMFILE;

        RequestParams params = new RequestParams();
        params.put("serial", _room_id);
        Log.e(TAG, "step2GetRoomFile: paramsL:" + params.toString());
        Log.e(TAG, "step2GetRoomFile: url:" + url);
        client.post(url, params, new JsonHttpResponseHandler() {
            @Override
            public void onSuccess(int statusCode, Header[] headers, final JSONObject response) {
                Log.e(TAG, "step2GetRoomFile:onSuccess: " + response);
                RunOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        String str = response.toString();
                        Log.d("RoomFile==", str);
                        JSONArray roomfile = null;
                        try {
                            int nRet = response.getInt("result");
                            roomfile = response.getJSONArray("roomfile");
                            for (int i = 0; i < roomfile.length(); i++) {
                                RoomFile file = new RoomFile();
                                file.setFileid(roomfile.getJSONObject(i).getString("fileid"));
                                file.setFilename(roomfile.getJSONObject(i).getString("filename"));
                                file.setNewfilename(roomfile.getJSONObject(i).getString("newfilename"));
                                file.setSize(roomfile.getJSONObject(i).getString("size"));
                                file.setDownloadpath(roomfile.getJSONObject(i).getString("downloadpath"));
                                file.setSwfpath(roomfile.getJSONObject(i).getString("swfpath"));
                                file.setPdfpath(roomfile.getJSONObject(i).getString("pdfpath"));
                                file.setFiletype(roomfile.getJSONObject(i).getString("filetype"));
                                file.setPagenum(roomfile.getJSONObject(i).getString("pagenum"));
                                file.setIsconvert(roomfile.getJSONObject(i).getString("isconvert"));
                                file.setState("0");
                                filesList.add(file);
                            }
                            onComplete.onComplete(nRet);
                        } catch (JSONException e) {
                            e.printStackTrace();
                            onComplete.onComplete(ERR_INTERNAL_EXCEPTION);
                        }

                    }
                });
            }

            @Override
            public void onFailure(final int statusCode, Header[] headers, String responseString, Throwable throwable) {
                Log.e(TAG, "step2GetRoomFile:onFailure: " + responseString);
                RunOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        onComplete.onComplete(ERR_HTTP_REQUEST_FAILED);
                    }
                });
            }

            @Override
            public void onFailure(final int statusCode, Header[] headers, Throwable throwable, JSONObject object) {
                Log.e(TAG, "step2GetRoomFile:onFailure:object: " + object);
                RunOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        onComplete.onComplete(ERR_HTTP_REQUEST_FAILED);
                    }
                });
            }
        });
    }

    private void step2GetConfig(final String ip, final String port, final ClassRoomManagerAsyncHandler onComplete) {
        setStatus(STATUS_GETTINGCFG);
        if (_room_id == null) {
            onComplete.onComplete(ERR_HTTP_REQUEST_FAILED);
            return;
        }

        String url = "https://" + ip + ":" + port + WEBFUNC_GETCONFIG;

        _room_ip = null;
        Log.e(TAG, "step2GetConfig: url:" + url);
        client.post(url, null, new JsonHttpResponseHandler() {
            @Override
            public void onSuccess(int statusCode, Header[] headers, final JSONObject response) {
                Log.e(TAG, "step2GetConfig:onSuccess: " + response);
                RunOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        Iterator<?> it = response.keys();
                        String key = "";
                        String v = "";
                        while (it.hasNext()) {
                            key = (String) it.next().toString();
                            if (null != key && key.startsWith("HOST_") && key.equals("HOST_" + ip)) {
                                v = response.optString(key);
                                _room_ip = v;
                                break;
                            }
                        }

                        onComplete.onComplete(0);
                    }
                });
            }

            @Override
            public void onFailure(final int statusCode, Header[] headers, String responseString, Throwable throwable) {
                RunOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        onComplete.onComplete(ERR_HTTP_REQUEST_FAILED);
                    }
                });
            }

            @Override
            public void onFailure(final int statusCode, Header[] headers, Throwable throwable, JSONObject object) {
                RunOnUIThread(new Runnable() {
                    @Override
                    public void run() {
                        onComplete.onComplete(ERR_HTTP_REQUEST_FAILED);
                    }
                });
            }
        });
    }

    private void step2InitMedia() {

        logd("step2InitMedia");
        if (!_ut) {
            NBMMediaConfiguration.NBMVideoFormat receiverVideoFormat = new NBMMediaConfiguration.NBMVideoFormat(320, 240, ImageFormat.NV21, 15);
            NBMMediaConfiguration config = new NBMMediaConfiguration(NBMMediaConfiguration.NBMRendererType.OPENGLES, NBMMediaConfiguration.NBMAudioCodec.OPUS, 0, NBMMediaConfiguration.NBMVideoCodec.VP8, 0, receiverVideoFormat, NBMMediaConfiguration.NBMCameraPosition.FRONT);

            _media = new NBMWebRTCPeer(config, ApplicationContext, _localView, this);
            _media.initialize();
        }
    }

    private void step3Connect() {

        logd("step3Connect");
        if (_status >= STATUS_CONNECTING)
            return;

        if (_executor == null) {
            _executor = new LooperExecutor();
            _executor.requestStart();
        }

        if (!_ut)
            _room = new KurentoRoomAPI(_executor, _room_uri, this);
        Log.e(TAG, "step3Connect: _room_uri:" + _room_uri);
        setStatus(STATUS_CONNECTING);
        _room.connectWebSocket();
    }

    private void step4Join() {

        if (_status >= STATUS_JOINING)
            return;

        setStatus(STATUS_JOINING);

        HashMap<String, Object> properties = new HashMap<String, Object>();
        properties.put("hasaudio", _myself.hasAudio);
        properties.put("hasvideo", _myself.hasVideo);
        properties.put("role", _myself.role);
        properties.put("nickname", _myself.nickName);
        properties.put("publishstate", 0);
        properties.put("candraw", false);
        properties.put("raisehand", false);
        properties.put("giftnumber", _giftinfo);

        _room.sendJoinRoom(_myself.peerId, _room_id, _hasDatachannel, properties, REQ(REQUEST_TYPE_JION));
    }

    // !!!must in main thread!!!
    private void checkIfCanJoin() {
        if (_status == STATUS_CONNECTED && _media != null && _media.isInitialized()) {
            step4Join();
        }
    }

    public int reShow() {
        step2InitMedia();
        return ERR_OK;
    }

    private void setStatus(int newStatus) {
        logd("New status " + newStatus);
        if (_status == newStatus)
            return;

        _status = newStatus;
        if (_status == STATUS_ALLREADY) {
            if (_cbk != null)
                _cbk.roomManagerRoomJoined();
        } else if (_status == STATUS_IDLE) {
            _media.enableAudio(false);
            _media.enableVideo(false);
            _media.stopLocalMedia();
            _media.close();

            if (_cbk != null)
                _cbk.roomManagerDidFinish();
        }
    }

    private int publishVideo(boolean doLoopback) {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        _media.generateOffer(_myself.peerId, true);
        return ERR_OK;
    }

    private int unpublishVideo() {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        _room.sendUnpublishVideo(REQ(REQUEST_TYPE_UNPUB, _myself.peerId));
        _media.closeConnection(_myself.peerId);
        return ERR_OK;
    }

    private int receiveVideoFromPeer(String peer) {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        _media.generateOffer(peer, false);
        return ERR_OK;
    }

    private int unsubscribeVideoFromPeer(String peer) {
        if (_status != STATUS_ALLREADY)
            return ERR_INVALID_STATUS;

        _room.sendUnsubscribeFromVideo(peer, "webcam", REQ(REQUEST_TYPE_UNSUB, peer, "webcam"));
        _media.closeConnection(peer);
        _cbk.roomManagerUserVideoUnPublished(peer);
        return ERR_OK;
    }

    // room msg processing
    private void onRemoteMessage(boolean add, Map<String, Object> message) {
        Log.e(TAG, "onRemoteMessage:msg: " + message);
        String id = message.containsKey("id") ? message.get("id").toString() : "";
        String name = message.containsKey("name") ? message.get("name").toString() : "";
        String data = message.containsKey("data") ? message.get("data").toString() : "";
        long ts = (message.containsKey("ts") && message.get("ts") instanceof Long) ? (Long) message.get("ts") : 0;
        if (add) {
            if (_cbk != null)
                _cbk.roomManagerOnRemoteMsg(add, id, name, "1", ts, data);
        } else {
            if (_cbk != null)
                _cbk.roomManagerOnRemoteMsg(add, id, name, "1", ts, message);
        }

    }

    private void onRemoteMessageList(boolean add, Map<String, Object> messages) {
        Log.e(TAG, "onRemoteMessageList:msg: " + messages);
        Map<String, Object> object = new HashMap<>();
        for (Object message : messages.values()) {
            if (message instanceof Map) {
                if ("ClassBegin".equals(((Map) message).get("name").toString())) {
                    object = (Map<String, Object>) message;
                    break;
                }
            }
        }

        String id = object.containsKey("id") ? object.get("id").toString() : "";
        String name = object.containsKey("name") ? object.get("name").toString() : "";
        long ts = (object.containsKey("ts") && object.get("ts") instanceof Long) ? (Long) object.get("ts") : 0;
        if (_cbk != null)
            _cbk.roomManagerOnRemoteMsg(add, id, name, "0", ts, messages);

        for (Object message : messages.values()) {
            if (message instanceof Map) {
                if ("UpdateTime".equals(((Map) message).get("name").toString())) {
                    object = (Map<String, Object>) message;
                    break;
                }
            }
        }
        String id1 = object.containsKey("id") ? object.get("id").toString() : "";
        String name1 = object.containsKey("name") ? object.get("name").toString() : "";
        long ts1 = (object.containsKey("ts") && object.get("ts") instanceof Long) ? (Long) object.get("ts") : 0;
        if (_cbk != null)
            _cbk.roomManagerOnRemoteMsg(add, id1, name1, "0", ts1, messages);

    }

    private void onChangeMyPublishState(int newState) {
        if (_myself.publishState == newState)
            return;

        if (newState > 0)
            publishVideo(false);
        else
            unpublishVideo();
    }

    // tool functions
    private boolean optBoolean(Map<String, Object> map, String key) {
        if (map.containsKey(key) && map.get(key) instanceof Boolean)
            return (Boolean) map.get(key);
        return false;
    }

    private int optInt(Map<String, Object> map, String key) {
        if (map.containsKey(key) && map.get(key) instanceof Number)
            return Integer.parseInt(map.get(key).toString());
        return 0;
    }

    private String optString(Map<String, Object> map, String key) {
        if (map.containsKey(key) && map.get(key) instanceof Long)
            return (String.valueOf(map.get(key))).trim();
        return "";
    }

    // room callbacks
    @Override
    public void onRoomResponse(final RoomResponse response) {
        Log.e(TAG, "onRoomResponse:1 " + response.toString());
        Request req = null;
        synchronized (_requests) {
            req = _requests.get(response.getId());
            _requests.remove(response.getId());
        }

        if (req == null)
            return;

        if (req.reqType == REQUEST_TYPE_JION) {
            Log.e(TAG, "Successfully connected to the room!");
            Log.e(TAG, "onRoomResponse: getValues" + response.getValues());
            RunOnUIThread(new Runnable() {
                @Override
                public void run() {

                    setStatus(STATUS_ALLREADY);
                    if (response.getValues() == null)
                        return;

                    for (Map<String, String> user : response.getValues()) {
                        ClassRoomUser classRoomUser = new ClassRoomUser(user);

                        Log.e(TAG, "USER:" + user.toString());
                        Log.e(TAG, "classRoomUser:" + classRoomUser.toString());
                        _users.put(classRoomUser.peerId, classRoomUser);

                        AllRoomUser.RoomUser roomUser = new AllRoomUser.RoomUser();
                        roomUser.setFirstname(classRoomUser.nickName);
                        roomUser.setAccount(classRoomUser.nickName);
                        roomUser.setReceiveid(classRoomUser.peerId);
                        _room_users.add(roomUser);
                        Log.e(TAG, "classRoomUser: " + classRoomUser.toString());
                        _cbk.roomManagerUserJoined(classRoomUser, true);

                        String data = user.get("streams");
                        Log.e(TAG, "run: data:" + data);
                        if (data != null && data instanceof String && !data.isEmpty())
                            receiveVideoFromPeer(classRoomUser.peerId);
                    }
                }
            });
        } else if (req.reqType == REQUEST_TYPE_PUB
                || req.reqType == REQUEST_TYPE_SUB) {
            SessionDescription.Type t = SessionDescription.Type.ANSWER;
            _media.processAnswer(new SessionDescription(t, response.getSdpAnswer()), req.reqParam);
        } else if (req.reqType == REQUEST_TYPE_UNPUB
                || req.reqType == REQUEST_TYPE_UNSUB) {
            _media.closeConnection(req.reqParam);
        }
    }

    public void startMedia(VideoRenderer.Callbacks callbacks) {
        _media.attachRendererToLocalStream(callbacks);
    }

    @Override
    public void onRoomError(RoomError error) {
        Log.e(TAG, "onRoomError: " + error.toString());
    }

    @Override
    public void onRoomNotification(RoomNotification notification) {
        Log.e(TAG, "onRoomNotification: Got " + notification.toString());
        if (notification.getMethod().equals(RoomListener.METHOD_SEND_MESSAGE)) {

            final String username = notification.getParam("user").toString();
            final String message = notification.getParam("message").toString();
            _cbk.roomManagerMessageReceived(username, message);

        } else if (notification.getMethod().equals(RoomListener.METHOD_ICE_CANDIDATE)) {

            String cad = notification.getParam("candidate").toString();

            if (Pattern.matches(".+([\\da-fA-F]{0,4}:){7}[\\da-fA-F]{0,4}.+", cad))//ipv6
                return;

            if (cad.indexOf("TCP") >= 0)
                return;

            if (_room_ip != null)
                cad = cad.replaceAll("(\\d+\\.){3}\\d+", _room_ip);

            logd(cad);
            IceCandidate ice = new IceCandidate(notification.getParam("sdpMid").toString(),
                    Integer.parseInt(notification.getParam("sdpMLineIndex").toString()),
                    cad);

            String name = (String) notification.getParam("endpointName");
            _media.addRemoteIceCandidate(ice, name);
        } else if (notification.getMethod().equals(RoomListener.METHOD_PARTICIPANT_JOINED)) {
            Log.e(TAG, "onRoomNotification: ");
            Map<String, Object> param = notification.getParams();
            String id = param.get("id").toString();

            if (param.containsKey("properties") && param.get("properties") instanceof Map) {
                try {
                    JSONObject jsonObject = new JSONObject(param.get("properties").toString());
                    Map<String, Object> properties = (Map<String, Object>) param.get("properties");

                    ClassRoomUser user = _users.get(id);
                    if (user == null) {
                        user = new ClassRoomUser();
                        user.peerId = id;
                    }

                    user.nickName = jsonObject.getString("nickname");

                    user.giftnumber = String.valueOf(jsonObject.optInt("giftnumber", 0));
                    user.hasAudio = optBoolean(properties, "hasaudio");
                    user.hasVideo = optBoolean(properties, "hasvideo");
                    user.canDraw = optBoolean(properties, "candraw");
                    user.role = optInt(properties, "role");
                    user.publishState = optInt(properties, "publishstate");
                    user.raiseHand = optBoolean(properties, "raisehand");

                    _users.put(user.peerId, user);
                    AllRoomUser.RoomUser roomUser = new AllRoomUser.RoomUser();
                    roomUser.setFirstname(user.nickName);
                    roomUser.setAccount(user.nickName);
                    roomUser.setReceiveid(user.peerId);
                    _room_users.add(roomUser);
                    _cbk.roomManagerUserJoined(user, false);

                } catch (JSONException e) {
                    e.printStackTrace();
                }

            }

            if (param.containsKey("streams"))
                receiveVideoFromPeer(id);
        } else if (notification.getMethod().equals(RoomListener.METHOD_PARTICIPANT_LEFT)) {
            String id = notification.getParam("name").toString();
            logd("participantLeft " + id);
            if (!_users.containsKey(id))
                return;

            ClassRoomUser user = _users.get(id);
            _users.remove(id);
            _cbk.roomManagerUserLeft(user);
        } else if (notification.getMethod().equals(RoomListener.METHOD_PARTICIPANT_PUBLISHED)) {
            String id = notification.getParam("id").toString();
            logd("participantPublished " + id);
            if (!_users.containsKey(id))
                return;

            receiveVideoFromPeer(id);
        } else if (notification.getMethod().equals(RoomListener.METHOD_PARTICIPANT_UNPUBLISHED)) {
            String id = notification.getParam("name").toString();
            logd("participantPublished " + id);
            if (!_users.containsKey(id))
                return;

            unsubscribeVideoFromPeer(id);
        } else if (notification.getMethod().equals("msgList")) {
            Log.e(TAG, "onRoomNotification: msgList");
            Map<String, Object> messages = notification.getParams();
            onRemoteMessageList(true, messages);
        } else if (notification.getMethod().equals("pubMsg")) {
            onRemoteMessage(true, notification.getParams());
        } else if (notification.getMethod().equals("delMsg")) {
            onRemoteMessage(false, notification.getParams());
        } else if (notification.getMethod().equals("setProperty")) {
            logd("setProperty");
            Map<String, Object> param = notification.getParams();
            String id = param.get("id").toString();

            if (param.containsKey("properties") && param.get("properties") instanceof Map) {
                Map<String, Object> properties = (Map<String, Object>) param.get("properties");
                ClassRoomUser user = _users.get(id);
                if (user == null) {
                    if (_myself.peerId.equals(id)) {
                        user = _myself;
                        if (properties.containsKey("publishstate"))
                            onChangeMyPublishState(optInt(properties, "publishstate"));
                    } else
                        return;
                }

                if (properties.containsKey("hasaudio"))
                    user.hasAudio = optBoolean(properties, "hasaudio");

                if (properties.containsKey("hasvideo"))
                    user.hasVideo = optBoolean(properties, "hasvideo");

                if (properties.containsKey("candraw"))
                    user.canDraw = optBoolean(properties, "candraw");

                if (properties.containsKey("publishstate")) {
                    user.publishState = optInt(properties, "publishstate");
                    Log.e(TAG, "onRoomNotification:publishstate; " + optInt(properties, "publishstate"));
                }
                if (properties.containsKey("raisehand")) {
                    user.raiseHand = optBoolean(properties, "raisehand");
                }

                if (properties.containsKey("giftnumber")) {
                    user.giftnumber = optString(properties, "giftnumber");
                }

                logd(param.toString());
                _cbk.roomManagerUserChanged(user);
            }
        } else if (notification.getMethod().equals("participantEvicted")) {
            logd("participantEvicted");
            _cbk.participantEvicted(_myself);
        }
    }

    @Override
    public void onRoomConnected() {
        Log.e(TAG, "onRoomConnected: ");
        RunOnUIThread(new Runnable() {
            @Override
            public void run() {
                setStatus(STATUS_CONNECTED);
                checkIfCanJoin();
            }
        });
    }

    @Override
    public void onRoomDisconnected() {
        loge("I got disconnected...");
        RunOnUIThread(new Runnable() {
            @Override
            public void run() {
                setStatus(STATUS_IDLE);
            }
        });
    }

    // PeerConnection callbacks
    @Override
    public void onInitialize() {
        logd("onInitialize");

        RunOnUIThread(new Runnable() {
            @Override
            public void run() {
                _media.enableAudio(true);
                _media.enableVideo(true);
                checkIfCanJoin();
            }
        });
    }

    private boolean isMyVideoPublished = false;

    @Override
    public void onLocalSdpOfferGenerated(SessionDescription sessionDescription, NBMPeerConnection nbmPeerConnection) {

        logd("onLocalSdpOfferGenerated " + nbmPeerConnection.getConnectionId());
        if (nbmPeerConnection.getConnectionId().equals(_myself.peerId)) {
            _room.sendPublishVideo(sessionDescription.description, false, REQ(REQUEST_TYPE_PUB, nbmPeerConnection.getConnectionId()));
        } else {
            _room.sendReceiveVideoFrom(nbmPeerConnection.getConnectionId(), "webcam", sessionDescription.description, REQ(REQUEST_TYPE_SUB, nbmPeerConnection.getConnectionId()));
        }
    }

    @Override
    public void onLocalSdpAnswerGenerated(SessionDescription sessionDescription, NBMPeerConnection nbmPeerConnection) {
        logd("onLocalSdpAnswerGenerated " + nbmPeerConnection.getConnectionId());
    }

    @Override
    public void onIceCandidate(IceCandidate iceCandidate, NBMPeerConnection nbmPeerConnection) {
        logd("onIceCandidate " + nbmPeerConnection.getConnectionId());
        String sdp = iceCandidate.sdp.replaceAll("(\\d+\\.){3}\\d+", "254.254.254.254");
        _room.sendOnIceCandidate(nbmPeerConnection.getConnectionId(), iceCandidate.sdp, iceCandidate.sdpMid, Integer.toString(iceCandidate.sdpMLineIndex), REQ(REQUEST_TYPE_ICE, nbmPeerConnection.getConnectionId()));
    }

    @Override
    public void onIceStatusChanged(PeerConnection.IceConnectionState iceConnectionState, NBMPeerConnection nbmPeerConnection) {
        logd("onIceStatusChanged " + nbmPeerConnection.getConnectionId() + " " + iceConnectionState);
        _cbk.roomManagerIceStatusChanged(nbmPeerConnection.getConnectionId(), iceConnectionState.toString());
    }

    @Override
    public void onRemoteStreamAdded(final MediaStream mediaStream, NBMPeerConnection nbmPeerConnection) {
        Log.e(TAG, "onRemoteStreamAdded" + nbmPeerConnection.getConnectionId());
        final String peerId = nbmPeerConnection.getConnectionId();
        if (peerId.equals(_myself.peerId))
            return;

        RunOnUIThread(new Runnable() {
            @Override
            public void run() {

                if (_cbk != null) {
                    Object view = _cbk.roomManagerUserVideoPublished(peerId);
                    if (view != null && view instanceof VideoRenderer.Callbacks) {
                        _media.attachRendererToRemoteStream((VideoRenderer.Callbacks) view, mediaStream);
                    }
                }
            }
        });
    }

    @Override
    public void onRemoteStreamRemoved(MediaStream mediaStream, NBMPeerConnection nbmPeerConnection) {
        logd("onRemoteStreamRemoved");
        final String peerId = nbmPeerConnection.getConnectionId();
        RunOnUIThread(new Runnable() {
            @Override
            public void run() {

                if (_cbk != null) {
                    _cbk.roomManagerUserVideoUnPublished(peerId);
                }
            }
        });
    }

    @Override
    public void onPeerConnectionError(String s) {
        loge(s);
    }

    @Override
    public void onDataChannel(DataChannel dataChannel, NBMPeerConnection nbmPeerConnection) {

    }

    @Override
    public void onBufferedAmountChange(long l, NBMPeerConnection nbmPeerConnection, DataChannel dataChannel) {

    }

    @Override
    public void onStateChange(NBMPeerConnection nbmPeerConnection, DataChannel dataChannel) {

    }

    @Override
    public void onMessage(DataChannel.Buffer buffer, NBMPeerConnection nbmPeerConnection, DataChannel dataChannel) {

    }
}
